/**
 * 自动下载 cctv 网页视频。
 *
 * 使用方式：
 *
 * ```shell
 * # 下载单个视频
 * deno run -A .\src\download\cctv.js http://tv.cctv.com/2022/08/04/VIDEs1EMO0zuANIxwbOvhKDm220804.shtml
 * # 下载整个视频合集
 * deno run -A .\src\download\cctv.js https://tv.cctv.com/2013/03/13/VIDA1363161600241941.shtml
 * # 打包 exe
 * deno compile -A --import-map=import-map.json -o temp/cctv-download.exe src/download/cctv.js
 * ```
 *
 * downloadOne 函数下载单个视频的代码逻辑说明：
 *
 * 1. 获取网页视频的在线播放地址，如 http://tv.cctv.com/2022/08/04/VIDEs1EMO0zuANIxwbOvhKDm220804.shtml
 * 2. 从此网页返回的 html 页面源码 “var guid = "45a6ada8624841f491f0b687af1093bc";” 解析出 guid 的值。
 * 3. 构建请求 “http://vdn.apps.cntv.cn/api/getHttpVideoInfo.do?pid=${guid}”，响应返回 json。
 * 4. 获取 json.video.chapters4 数组的值，此值包含了整个视频分拆成的片段，分辨率为 1280x720p，数据格式为：
 * ```json
 * [
 *   {
 *     "duration": "120.00",
 *     "image": "http://p4.img.cctvpic.com/fmspic/2022/08/04/45a6ada8624841f491f0b687af1093bc-1.jpg",
 *     "url": "http://vod.cntv.myhwcdn.cn/flash/mp4video63/TMS/2022/08/04/45a6ada8624841f491f0b687af1093bc_h2642000000nero_aac16-1.mp4"
 *   },
 *   {
 *     "duration": "120.00",
 *     "image": "http://p4.img.cctvpic.com/fmspic/2022/08/04/45a6ada8624841f491f0b687af1093bc-1.jpg",
 *     "url": "http://vod.cntv.myhwcdn.cn/flash/mp4video63/TMS/2022/08/04/45a6ada8624841f491f0b687af1093bc_h2642000000nero_aac16-2.mp4"
 *   },
 *   {
 *     "duration": "120.00",
 *     "image": "http://p4.img.cctvpic.com/fmspic/2022/08/04/45a6ada8624841f491f0b687af1093bc-1.jpg",
 *     "url": "http://vod.cntv.myhwcdn.cn/flash/mp4video63/TMS/2022/08/04/45a6ada8624841f491f0b687af1093bc_h2642000000nero_aac16-3.mp4"
 *   },
 *   {
 *     "duration": "57.88",
 *     "image": "http://p4.img.cctvpic.com/fmspic/2022/08/04/45a6ada8624841f491f0b687af1093bc-1.jpg",
 *     "url": "http://vod.cntv.myhwcdn.cn/flash/mp4video63/TMS/2022/08/04/45a6ada8624841f491f0b687af1093bc_h2642000000nero_aac16-4.mp4"
 *   }
 * ]
 * ```
 * 5. 其中 url 的值 “*.mp4” 为没有加密的视频片段，下载后合并在一起就是整个视频。
 * 6. 视频的标题为 json.title 的值，视频总长度为 json.video.totalLength 的值（单位为秒）。
 */

import { writableStreamFromWriter } from "/deno.land/std/streams/mod.ts";
import { parse } from "/deno.land/std/flags/mod.ts";
import * as path from "/deno.land/std/path/mod.ts";
import { exists } from "../share/file.js";
import { log } from "../share/common.js";

log(`cctv 网页视频下载器 v230103T2000 @RJ`);

// 解析命令行参数
const args = parse(Deno.args);

// 1. 从命令行参数获取视频地址
if (args["_"].length !== 1) {
  log(`没有指定视频地址！参考例子：
  1. 若执行 “cctv-download.exe https://tv.cctv.com/2022/08/01/VIDEsglZ8zARaPsEz9XREo44220801.shtml” 将下载单个视频 “《中华勤学故事》 第1集 范仲淹断齑划粥.mp4”。
  2. 若执行 “cctv-download.exe https://tv.cctv.com/2013/03/13/VIDA1363161600241941.shtml” 将下载整个视频集 “《中华勤学故事》共55集”。
  视频默认下载到当前目录，如需指定，添加 “-o "\\path\\to"” 指定目录即可，如：
  cctv-download.exe -o "d:\\temp" https://tv.cctv.com/2013/03/13/VIDA1363161600241941.shtml
  `);
  Deno.exit();
}

log(`开始...`);
const url = args["_"][0];
const targetDir = Object.hasOwn(args, "o") ? args["o"] : Deno.cwd();
const tempDir = path.join(targetDir, "temp");
if (url.includes("/VIDE")) await downloadOne(url, targetDir, tempDir, true);
else if (url.includes("/VIDA")) await downloadTag(url, targetDir, tempDir, Object.hasOwn(args, "d"));
else {
  log(`视频地址格式错误，只支持如下两种格式：
  1. 单个视频 https://tv.cctv.com/.../VIDE....shtml，如 https://tv.cctv.com/2022/08/01/VIDEsglZ8zARaPsEz9XREo44220801.shtml
  2. 视频合集 https://tv.cctv.com/.../VIDA....shtml，如 https://tv.cctv.com/2013/03/13/VIDA1363161600241941.shtml`);
  Deno.exit();
}
log(`结束`);

/**
 * 下载整个视频集。
 * 参数 tagUrl 为网站的视频集地址，格式为 “https://tv.cctv.com/2013/03/13/VIDA1363161600241941.shtml”。
 * 地址中含关键字 “VIDA”。
 */
async function downloadTag(tagUrl, targetDir, tempDir, debug) {
  const bodyText = await fetch(tagUrl).then((res) => res.text());
  // 1. 解析合集名称
  const tagName = bodyText.match(/var commentTitle = "(.+)";/)[1];
  log(`视频合集名称=${tagName}`);

  // 2. 解析视频合集各视频地址
  let arrayVar = bodyText.match(/(?<=(var jsonData1=))[\s\S]+?(?=(\;))/m); // 粗切 正片
  let list = eval(`(${arrayVar[0]})`); // 因为json 属性使用了单引号，因此不能使用 JSON.parse
  if (!list || list.length === 0) {
    arrayVar = bodyText.match(/(?<=(var jsonData2=))[\s\S]+?(?=(\;))/m); // 精切 花絮
    list = eval(`(${arrayVar[0]})`);
  }
  //log(JSON.stringify(list, null, 2));

  // 3. 循环下载合集的各个视频
  log(`该视频合集共有 ${list.length} 个视频，开始逐个下载...`);
  let i = 0;
  for await (const item of list) {
    log(`开始下载 ${++i}/${list.length} ${item.title}`);
    log(`  ${item.url}`);
    if (!(await exists(`${path.join(targetDir, tagName, item.title)}.mp4`))) {
      await downloadOne(item.url, path.join(targetDir, tagName), path.join(tempDir, tagName), debug);
    } else log(`  视频已经存在，忽略不重复下载！`);
  }
}

/**
 * 下载单个视频。
 * 参数 videoUrl 为网站的视频播放地址，格式为 “http://tv.cctv.com/2022/08/04/VIDEs1EMO0zuANIxwbOvhKDm220804.shtml”。
 * 地址中含关键字 “VIDE”。
 */
async function downloadOne(videoUrl, targetDir, tempDir, debug) {
  if (debug) log(`抓取视频主页...${videoUrl}`);

  // 2. 获取视频 guid 的值
  const content = await fetch(videoUrl).then((res) => res.text());
  const guid = content.match(/var guid = "(.+)";/)[1];
  if (debug) log(`视频 guid=${guid}`);

  // 3. 获取视频分段数据
  const url4videos = `http://vdn.apps.cntv.cn/api/getHttpVideoInfo.do?pid=${guid}`;
  if (debug) log(`抓取视频分段信息...${url4videos}`);
  const json = await fetch(url4videos).then((res) => res.json());
  let chapters; // chapters、chapters1、chapters2、chapters3、chapters4 数字越大清晰度越高
  if (Object.hasOwn(json.video, "chapters4")) chapters = json.video.chapters4;
  else if (Object.hasOwn(json.video, "chapters3")) chapters = json.video.chapters3;
  else if (Object.hasOwn(json.video, "chapters2")) chapters = json.video.chapters2;
  else if (Object.hasOwn(json.video, "chapters1")) chapters = json.video.chapters1;
  else chapters = json.video.chapters;
  const video = {
    name: json.title,
    length: json.video.totallength,
    urls: chapters.map((i) => i.url),
  };
  if (debug) log(`视频分段信息=${JSON.stringify(video, null, 2)}`);

  // 4. 创建视频下载到的目录
  if (!(await exists(tempDir))) await Deno.mkdir(tempDir, { recursive: true });
  if (!(await exists(targetDir))) await Deno.mkdir(targetDir, { recursive: true });

  // 5. 下载各视频片段
  let i = 0;
  for await (const url of video.urls) {
    i++;
    const name = path.basename(url);
    const fileName = path.join(tempDir, name);
    // 如果视频片段已经存在则忽略，不重复下载
    if (!(await exists(fileName))) {
      log(`下载视频“${video.name}”分段 ${i}/${video.urls.length}...`);
      const fileResponse = await fetch(url);
      if (fileResponse.body) {
        const file = await Deno.open(fileName, { write: true, create: true });
        const writableStream = writableStreamFromWriter(file);
        await fileResponse.body.pipeTo(writableStream);
        log(`保存视频“${video.name}”分段 ${i}/${video.urls.length} 到 ${fileName}`);
      }
    } else {
      log(`视频“${video.name}”分段 ${i}/${video.urls.length} 已经存在，忽略不重复下载！`);
    }
  }

  // 6. 用 ffmpeg 合并视频片段
  const mergeFile = path.join(targetDir, `${video.name}.mp4`);
  if (await exists(mergeFile)) {
    log(`视频 ${mergeFile} 已经存在，忽略不重复合并！`);
  } else {
    log(`开始合并视频“${video.name}”各分段...`);
    const mergeCfgFile = path.join(tempDir, `${guid}.txt`);
    if (await exists(mergeCfgFile)) await Deno.remove(mergeCfgFile);
    await Deno.writeTextFile(
      mergeCfgFile,
      video.urls.map((url) => `file '${path.join(tempDir, path.basename(url))}'`).join("\r\n"),
    );

    const cmdArgs = [
      ...`ffmpeg -strict strict -v error -stats -f concat -safe 0 -i ${mergeCfgFile} -c copy`.split(" "),
      mergeFile,
    ];
    //log(`cmdArgs ${JSON.stringify(cmdArgs, null, 2)}`);
    await Deno.run({ cmd: cmdArgs }).status();
    log(`保存合并视频到 ${mergeFile}`);
  }
}
